#include "fileTransfer.h"

/*
 * @brief 客户端请求上传文件
 *
 * @param sockfd 连接服务端的socket文件描述符
 * @param file_path 需要上传的文件路径（不支持上传目录）
 *
 * @return 成功返回 0
 */
int upload(int sockfd, const char *file_path)
{
    // 必须输入有效路径
    // 获取file_name
    char file_name[FILE_NAME_MAX];
    const char *last_slash = strrchr(file_path, '/');

    const char *start = last_slash ? (last_slash + 1) : file_path;

    size_t len = strlen(start);
    if (len == 0)
    {
        return -1;
    }
    if (len >= sizeof(file_name))
    {
        return -1;
    }

    strncpy(file_name, start, FILENAME_MAX - 1);
    file_name[FILENAME_MAX - 1] = '\0'; // 确保结束符

    tlv_t client_tlv;
    fileCheck_t file_check;
    int flag;      // 传输校验码
    int type;      // 传输类型
    int mmap_flag; // 使用mmap传输标识

    // 获取hash、filesize
    int fd = open(file_path, O_RDONLY);
    off_t current_file_size;
    calculateSHA256(fd, file_check.sha256);   // file_hash
    getLocalFileSize(fd, &current_file_size); // file_size
    file_check.file_size = current_file_size;
    close(fd);

    // 发送hash校验文件
    client_tlv.type = TYPE_METADATA;
    client_tlv.length = SHA256_LENGTH;
    send(sockfd, &client_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
    send(sockfd, &file_check.sha256, client_tlv.length, MSG_NOSIGNAL);

    // 接收并处理服务端返回的校验码
    recv(sockfd, &client_tlv, TLV_HEAD_SIZE, MSG_WAITALL);
    flag = client_tlv.length;
    type = client_tlv.type;

    // 获取请求上传文件元数据
    snprintf(file_check.file_name, sizeof(file_check.file_name), "%s", file_name); // 复制字符串
    file_check.file_offset = 0;

    // 发送file_check给服务端
    client_tlv.type = TYPE_FILE_CHECK;
    client_tlv.length = sizeof(fileCheck_t);
    send(sockfd, &client_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
    send(sockfd, &file_check, client_tlv.length, MSG_NOSIGNAL);

    if (type == TYPE_FLAG && flag == FILE_EXIST)
    {
        printf("一秒就传完鸟\n");
    }
    else if (type == TYPE_FLAG && flag == FILE_NOT_EXIST)
    {
        printf("准备上传鸟\n");
        int fd = open(file_path, O_RDONLY);
        mmap_flag = 1;
        clientSendData(sockfd, fd, &file_check, mmap_flag);
    }
    else if (type == TYPE_FLAG && flag == FILE_RESUME)
    {
        printf("准备续传鸟\n");
        // 只收offset
        recv(sockfd, &client_tlv, TLV_HEAD_SIZE, MSG_WAITALL);
        recv(sockfd, &file_check.file_offset, client_tlv.length, MSG_WAITALL);

        int fd = open(file_path, O_RDONLY, 0644);
        lseek(fd, file_check.file_offset, SEEK_SET); // 从文件开头偏移 offset
        mmap_flag = 0;
        clientSendData(sockfd, fd, &file_check, mmap_flag);
    }

    else
    {
        printf("传错了，蒜鸟蒜鸟\n");
        // 停止上传，重新请求
        return -1;
    }

    return 0;
}

int clientSendData(int sockfd, int fd, fileCheck_t *file_check, int mmap_flag)
{
    tlv_t send_tlv;
    off_t file_size = file_check->file_size; // 获取文件长度（实现进度条打印）
    printf("filesize = %ld\n", file_size);

    off_t cur_size = file_check->file_offset; // 已经上传的文件数据量
    off_t last_size = cur_size;               // 上次打印的cur_size
    off_t slice = file_size / 1000;           // 进度条分片

    printf("upload begin\n");

    // 传输文件
    if (file_size > BIG_FILE_SIZE && mmap_flag) // 大文件：mmap 实现零拷贝
    {
        // ftruncate(fd, file_size);

        // 建立映射区
        char *p = (char *)mmap(NULL, file_size,
                               PROT_READ, MAP_SHARED, fd, 0); // 权限只读

        off_t offset = file_check->file_offset; // 偏移量
        while (1)
        {
            if (offset >= file_size) // 跳出循环条件
            {
                break;
            }
            if (file_size - offset > CHUNK_SIZE) // 未传输数据量
            {
                send_tlv.length = CHUNK_SIZE;
            }
            else // 最后一次传输
            {
                send_tlv.length = file_size - offset;
            }

            send_tlv.type = TYPE_FILE_CHUNK;
            send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
            send(sockfd, p + offset, send_tlv.length, MSG_NOSIGNAL); // 从内核态到内核态
            offset += send_tlv.length;

            // 打印进度条
            cur_size += send_tlv.length;
            // 当前发送文件数据量 - 上次打印时发送文件数据量 > 分片，更新进度条
            if (cur_size - last_size > slice)
            {
                printf("send %.2lf%%\r", 100.0 * cur_size / file_size); // 打印已经完成发送的百分比
                fflush(stdout);                                         // 刷新缓冲区
                last_size = cur_size;                                   // 更新上一次
            }
        }

        send_tlv.type = TYPE_ABORT;
        send_tlv.length = 0;
        send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL); // 发一个空车厢

        // 释放映射区
        munmap(p, file_size);
    }
    else // 小文件
    {
        char *data = (char *)malloc(CHUNK_SIZE);
        while (1)
        {
            bzero(data, CHUNK_SIZE);                         // 清空车厢
            ssize_t ret_read = read(fd, data, sizeof(data)); // 将文件内容读取到火车厢
            send_tlv.length = ret_read;
            if (ret_read == 0) // 最后一次read已经没有数据了，ret=0，此时是一个空车厢
            {
                send_tlv.type = TYPE_ABORT;
                send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
                break;
            }
            send_tlv.type = TYPE_FILE_CHUNK;
            send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
            send(sockfd, &data, send_tlv.length, MSG_NOSIGNAL);

            // 打印进度条
            cur_size += send_tlv.length;
            // 当前发送文件数据量 - 上次打印时发送文件数据量 > 分片，更新进度条
            if (cur_size - last_size > slice)
            {
                printf("send %.2lf%%\r", 100.0 * cur_size / file_size); // 打印已经完成发送的百分比
                fflush(stdout);                                         // 刷新缓冲区
                last_size = cur_size;                                   // 更新上一次
            }
        }
        free(data);
    }
    printf("upload 100.00%%\n");

    close(fd);
    return 0;
}

int getLocalFileSize(const int fd, off_t *file_size)
{
    struct stat file_stat;

    if (fstat(fd, &file_stat) == -1)
    {
        perror("fstat() failed");
        return -1;
    }

    *file_size = file_stat.st_size;

    return 0;
}